<?php
/*
   Copyright (c) [2020] [liyunde]
   [open-client-phpsdk] is licensed under Mulan PSL v2.
   You can use this software according to the terms and conditions of the Mulan PSL v2.
   You may obtain a copy of Mulan PSL v2 at:
            http://license.coscl.org.cn/MulanPSL2
   THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
   See the Mulan PSL v2 for more details.
 */

namespace LIYunde\Cloud\Api\Util;

use Exception;
use LIYunde\Cloud\Api\Error\InvalidKeySpecException;
use LIYunde\Cloud\Api\Error\OpenSignException;

/**
 * Class OpenApiSignature
 * @author liyunde
 * @since 2020/8/23
 *
 * @package LIYunde\Cloud\Api\Util
 */
class OpenApiSignature {

    /** RSA最大加密明文大小  */
    const MAX_ENCRYPT_BLOCK = 117;

    /** RSA最大解密密文大小   */
    const MAX_DECRYPT_BLOCK = 128;

    const SIGN_TYPE_RSA = 'RSA';

    /**
     * sha256WithRsa 算法请求类型
     */
    const SIGN_TYPE_RSA2 = 'RSA2';

    const SIGN_ALGORITHMS = 'SHA256';

    const SIGN_ALGORITHMS_SHA1 = 'SHA1WithRSA';

    const SIGN_SHA256RSA_ALGORITHMS = 'SHA256WithRSA';

    /**
     * OpenApiSignature constructor.
     */
    public function __construct() {
    }

    public static function getSignContent(array $sortedParams) {
        $content = array_filter($sortedParams);

        ksort($content);

        $stringToBeSigned = "";

        $i = 0;
        foreach ($content as $k => $v) {
            if ($v && is_string($v) && '@' == substr($v, 0, 1)) {
                continue;
            }
            if (false === self::checkEmpty($v)) {

                // 转换成目标字符集
//                $v = $this->characet($v, $this->postCharset);

                if ($i == 0) {
                    $stringToBeSigned .= "$k=$v";
                } elseif (is_string($v)) {
                    $stringToBeSigned .= "&$k=$v";
                } else {
                    $stringToBeSigned .= "&$k=" . json_encode($v, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
                }
                $i++;
            }
        }

        unset($k, $v);
        return $stringToBeSigned;
    }

    /**
     * 转换字符集编码
     * @param $data
     * @param $targetCharset
     * @return string
     */
    public static function charset($data, $targetCharset) {

//        if (!empty($data)) {
//            $fileType = $this->fileCharset;
//            if (strcasecmp($fileType, $targetCharset) != 0) {
//                $data = mb_convert_encoding($data, $targetCharset, $fileType);
//                $data = iconv($fileType, $targetCharset . '//IGNORE', $data);
//            }
//        }
        return $data;
    }

    /**
     * 校验$value是否非空
     *
     * @param $value
     * @return bool
     * @author liyunde
     */
    protected static function checkEmpty($value) {
        return (!isset($value) || $value === null || (is_array($value) ? empty($value) : trim($value) === ""));
    }

    /**
     * sha256WithRsa 加签
     *
     * @param $content
     * @param $privateKey
     * @param $charset
     * @return String
     * @
     * @throws OpenSignException
     * @author liyunde
     */
    public static function rsa256Sign($content, $privateKey, $charset) {
        try {
            $binary_signature = "";

            $key = openssl_get_privatekey($privateKey);
            openssl_sign($content, $binary_signature, $key, self::SIGN_ALGORITHMS);

            openssl_free_key($key);

            return base64_encode($binary_signature);
        } catch (Exception $e) {
            throw new OpenSignException("RSA content = $content ; charset = $charset", $e);
        }
    }

    /**
     * @param $content
     * @param $sign
     * @param $publicKey
     * @param $charset
     * @return bool
     * @throws OpenSignException
     * @author liyunde
     */
    public static function verifyContentRsa256Sign($content, $sign, $publicKey, $charset) {
        $key = openssl_pkey_get_public($publicKey);

        if ($key) {
            $ok = openssl_verify($content, base64_decode($sign), $key, self::SIGN_ALGORITHMS);
            openssl_free_key($key);
            return (bool)$ok;
        }

        throw new OpenSignException(403, "内容:content= $content ,sign= $sign ,charset= $charset,err=公钥不能使用");
    }

    /**
     * rsa内容签名
     * @param $content
     * @param $privateKey
     * @param $charset
     * @param $signType
     * @return String
     *
     * @throws OpenSignException
     * @author liyunde
     */
    public static function rsaSign($content, $privateKey, $charset, $signType) {
        if (self::SIGN_TYPE_RSA2 == $signType) {
            return self::rsa256Sign($content, $privateKey, $charset);
        } elseif (self::SIGN_TYPE_RSA == $signType) {
            return self::rsa1Sign($content, $privateKey, $charset);
        } else {
            throw new OpenSignException(503, "Sign Type is Not Support : signType=$signType");
        }
    }

    /**
     * @param $content
     * @param $sign
     * @param $publicKey
     * @param $charset
     * @param $signType
     * @return bool|string
     * @throws OpenSignException
     * @author liyunde
     */
    public static function verifyContentRsaSign($content, $sign, $publicKey, $charset, $signType) {
        if (self::SIGN_TYPE_RSA2 == $signType) {
            return self::verifyContentRsa256Sign($content, $sign, $publicKey, $charset);
        }

        throw new OpenSignException(405, "Sign Type is Not Support : signType=$signType");
    }


    /**
     * sha1WithRsa 加签
     *
     * @param $content
     * @param $privateKey
     * @param $charset
     * @return string
     *
     * @throws OpenSignException
     *
     * @deprecated
     */
    public static function rsa1Sign($content, $privateKey, $charset) {
        try {
            $binary_signature = "";

            $key = openssl_get_privatekey($privateKey);

            if ($key) {
                openssl_sign($content, $binary_signature, $key, self::SIGN_ALGORITHMS_SHA1);

                openssl_free_key($key);

                return base64_encode($binary_signature);
            }
            throw new InvalidKeySpecException("RSA私钥格式不正确，请检查是否正确配置了PKCS1格式的私钥");
        } catch (Exception $e) {
            throw new OpenSignException($e->getCode(), "RSAcontent = $content; charset = $charset", $e);
        }
    }

    /**
     * @param array $params
     * @param $privateKey
     * @param $charset
     * @return String
     * @throws OpenSignException
     * @since 2020/8/23 11:49
     * @author liyunde
     */
    public static function rsaSignForm(array $params, $privateKey, $charset) {
        $signContent = self::getSignContent($params);
        return self::rsaSign($signContent, $privateKey, $charset);
    }
}
